package org.dslintegration.merge.km3.converter;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.dslintegration.history.EMetamodelSource;
import org.dslintegration.history.MergeAction;
import org.dslintegration.history.MergeHistory;
import org.dslintegration.kM3.Feature;
import org.dslintegration.kM3.KM3Class;
import org.dslintegration.kM3.Literal;
import org.dslintegration.kM3.Multiplicity;
import org.dslintegration.kM3.TypeRef;
import org.dslintegration.merge.InstanceLoader;
import org.dslintegration.merge.km3.core.Attribute;
import org.dslintegration.merge.km3.core.Class;
import org.dslintegration.merge.km3.core.Classifier;
import org.dslintegration.merge.km3.core.DataType;
import org.dslintegration.merge.km3.core.EnumLiteral;
import org.dslintegration.merge.km3.core.Enumeration;
import org.dslintegration.merge.km3.core.Metamodel;
import org.dslintegration.merge.km3.core.Package;
import org.dslintegration.merge.km3.core.Reference;
import org.dslintegration.merge.km3.core.StructuralFeature;
import org.dslintegration.merge.km3.exporter.KM3MetamodelExporter;

/**
 * Converts from EMF Km3 to km3
 * 
 * @author Stefan Martinov <stefan.martinov@gmail.com>
 * 
 */
public class KM3Converter {
	public EMetamodelSource metamodelSource;
	public MergeHistory history = new MergeHistory();

	public static void main(String[] args) throws Exception {
		org.dslintegration.kM3.Metamodel mml = InstanceLoader
				.loadMetamodel(new File("C:/Projects/Master/org.dslintegration.MetaModelMerge/src/org/dslintegration/merge/inputB.km3"));
		KM3Converter converter = new KM3Converter();
		Metamodel mm = converter.convert(mml);
		String stringMM = KM3MetamodelExporter.export(mm);

		org.dslintegration.kM3.Metamodel converted = InstanceLoader.loadMetamodel(new ByteArrayInputStream(stringMM.getBytes()));
		converted.eResource().save(System.out, Collections.EMPTY_MAP);
	}

	public HashMap<Class, List<String>> superTypeLinking = new HashMap<Class, List<String>>();
	public HashMap<StructuralFeature, String> typeLinking = new HashMap<StructuralFeature, String>();
	public HashMap<Reference, String> oppositeLinking = new HashMap<Reference, String>();
	public HashMap<String, Classifier> classifiers = new HashMap<String, Classifier>();
	public HashMap<String, Reference> references = new HashMap<String, Reference>();

	public KM3Converter() {

	}

	public Metamodel convert(org.dslintegration.kM3.Metamodel emfMm) {
		Metamodel metamodel = new Metamodel("");

		for (org.dslintegration.kM3.Package p : emfMm.getPackages()) {
			Package pack = new Package(p.getName());
			MergeAction action = MergeAction.getCopy(EMetamodelSource.LEFT, pack, EMetamodelSource.OUTPUT, pack);
			history.add(action);
			for (org.dslintegration.kM3.Classifier c : p.getClassifiers()) {
				pack.addContent(convert(c, pack));
			}

			metamodel.addPackage(pack);
		}
		// LINK ALL THE THINGS
		doLinking();
		return metamodel;
	}

	protected void doLinking() {
		for (StructuralFeature feature : typeLinking.keySet()) {
			feature.setType(classifiers.get(typeLinking.get(feature)));
		}

		for (Reference ref : oppositeLinking.keySet()) {
			ref.setOpposite(references.get(oppositeLinking.get(ref)));
		}

		// Link the supertypes
		System.out.println("Linking Supertypes");
		for (Classifier clazz : classifiers.values()) {
			if (clazz instanceof Class) {
				if (((Class) clazz).getSuperType() != null) {
					String supertype = clazz.getParent().getName().toLowerCase().substring(0, 1) + clazz.getParent().getName().substring(1, clazz.getParent().getName().length())
							+ "." + ((Class) clazz).getSuperType();
					((Class) clazz).setSuperTypeRef((Class) classifiers.get(supertype));
				}
			}
		}
	}

	/**
	 * 
	 * @param emfc
	 *            - emf KM3 classifier
	 * @param p
	 *            - parent package
	 * @return
	 */
	protected Classifier convert(org.dslintegration.kM3.Classifier emfc, Package p) {
		Classifier classifier = null;
		if (emfc instanceof org.dslintegration.kM3.Datatype) {
			DataType datatype = new DataType(emfc.getName());
			classifier = datatype;
		}

		if (emfc instanceof org.dslintegration.kM3.Enumeration) {
			Enumeration enumeration = new Enumeration(emfc.getName());
			for (Literal l : ((org.dslintegration.kM3.Enumeration) emfc).getLiterals()) {
				enumeration.addEnumLiteral(new EnumLiteral(l.getName()));
			}
			classifier = enumeration;
		}

		if (emfc instanceof org.dslintegration.kM3.KM3Class) {
			org.dslintegration.kM3.KM3Class old = (org.dslintegration.kM3.KM3Class) emfc;
			Class clazz = new Class(emfc.getName(), ((org.dslintegration.kM3.KM3Class) emfc).isAbstract());
			clazz.setSuperType(old.getSuperType() != null ? old.getSuperType().getName() : null);
			// TODO: Super types to fetch all the types FETCH ALL THE TYPES (Maybe in the linking phaze)
			classifier = clazz;
		}

		classifier.setParent(p);
		classifier.setLocation(p.getQName());
		classifiers.put(classifier.getQName(), classifier);
		// WRite to history
		MergeAction action = MergeAction.getCopy(metamodelSource, classifier, EMetamodelSource.OUTPUT, classifier);
		history.add(action);
		// Link if class
		if (classifier instanceof Class) {
			if (((org.dslintegration.kM3.KM3Class) emfc).getSuperType() != null)
				superTypeLinking.put((Class) classifier, getSuperTypeNames((KM3Class) emfc));

			for (Feature feature : ((org.dslintegration.kM3.KM3Class) emfc).getFeatures()) {
				((Class) classifier).addStructuralFeature(convert((Class) classifier, feature));
			}
		}

		return classifier;
	}

	protected StructuralFeature convert(Class host, Feature feature) {
		StructuralFeature sfeature = null;

		if (feature instanceof org.dslintegration.kM3.Attribute) {
			Attribute attribute = new Attribute();
			sfeature = attribute;
		}

		if (feature instanceof org.dslintegration.kM3.Reference) {
			Reference ref = new Reference(null, null);
			ref.setContainer(((org.dslintegration.kM3.Reference) feature).isContainer());
			if (!((org.dslintegration.kM3.Reference) feature).getOpposite().isEmpty()) {
				oppositeLinking.put(ref, ((org.dslintegration.kM3.Reference) feature).getOpposite().get(0).getName());
			}
			sfeature = ref;

			references.put(feature.getName(), ref);
		}

		sfeature.setName(feature.getName());
		Multiplicity mult = feature.getMultiplicity();
		int lower = mult != null ? mult.getLowerBound() : 0;
		int upper = mult != null ? mult.getUpperBound() : 1;
		sfeature.setParent(host);
		sfeature.setLower(lower);
		sfeature.setUpper(upper);

		String type = feature.getTypeRef().get(0).getType().get(0).getName();
		String pack = ((org.dslintegration.kM3.Package) feature.getTypeRef().get(0).getType().get(0).eContainer()).getName();
		typeLinking.put(sfeature, pack.substring(0, 1).toLowerCase() + pack.substring(1) + "." + type);

		// History
		MergeAction action = MergeAction.getCopy(metamodelSource, sfeature, EMetamodelSource.OUTPUT, sfeature);
		history.add(action);
		return sfeature;
	}

	public List<String> getSuperTypeNames(KM3Class clz) {
		ArrayList<String> list = new ArrayList<String>();
		KM3Class clazz = clz;
		while ((clazz = clazz.getSuperType()) != null) {
			String pname = ((org.dslintegration.kM3.Package) clazz.eContainer()).getName();
			String cname = clazz.getName();
			list.add(pname.toLowerCase() + "." + cname);
		}
		return list;
	}

	public EMetamodelSource getMetamodelSource() {
		return metamodelSource;
	}

	public void setMetamodelSource(EMetamodelSource metamodelSource) {
		this.metamodelSource = metamodelSource;
	}

	public MergeHistory getHistory() {
		return history;
	}

	public void setHistory(MergeHistory history) {
		this.history = history;
	}

}
